%{
#include <stdio.h>
#include <string.h>
#include "symtable.h"
#include "y.tab.h"
#include "ast_node.h"
#include "translation.h"
#include "global.h"
 
void yyerror(const char *str)
{
        fprintf(stderr,"%d: %s\n", lines, str);
}
 
int yywrap()
{
        return 1;
} 

struct entry struct_sprite[] = {
	"x", INTEGER, 0,
	0, 0, 0,
};

struct struct_entry basic_structs[] = {
	"sprite", struct_sprite,
	0, 0,
};
int last_struct = 0;

// Game Abstract Syntax Tree node
struct ast_node *game_node;

// Load initial entries in the symbol table
void init() {
	ast_table_lastentry = -1;
	struct_table_lastentry = -1;

	insert("int", INTEGER);
	insert("float", FLOAT);
	insert("string", STRING);
}

int main()
{
	lines = 1;
	scope = 0;

	// Initialise game AST
	game_node = ast_create(GAME, 0);

	init();
        yyparse();

	ast_print(game_node);
	// not ready for translation yet
	//translate(game_node);

	/* prints any node in ast that was leftover,
	 * this should always print nothing,
	 * unless there was an error or a bug
	 */
	printf("unused ast:\n");
	for (int i = 0; i <= ast_table_lastentry; i++) {
		ast_print(ast_table[i]);
	}

	return 0;
} 

%}

%token GAME WORLD STRUCT DEFINE NODE

/* Nodes types */
%token FUNCTION_DECLARATION FUNCTION_CALL

%token ID VAR_TYPE DOT

%token NUMBER

/* variable types */
%token UNDEFINED INTEGER FLOAT STRING

%%

/* Game definition */
game:
      	| game world
	{
		// if world was created, add it to game
		if ($2 != -1) {
			ast_child_add(game_node, ast_table[$2]);
		}
	}
	| game struct
	{
		// if struct was created, add it to game
		if ($2 != -1) {
			ast_child_add(game_node, struct_table[$2]);
		}
	}
	| game error ';'
	;

/* defines a struct of data
 * can include variables and functions with their statements
 * returns the index of the newly created struct
 * or -1 if something goes wrong (for example if the struct name is already used)
 */
struct:
	STRUCT ID new_scope multiple_declaration_statement scope_end ';'
	{
		// identifier not in use, initialise struct
		if (symtable[$2].token == UNDEFINED) {

			// mark identifier as defined struct
			symtable[$2].token = STRUCT;

			// give the identifier's index to the struct
			ast_table[$4]->value = $2;

			// initialise struct in struct table
			struct_table_lastentry++;
			struct_table[struct_table_lastentry] = ast_table[$4];

			// make sure struct's identifier includes the struct's location in the table
			symtable[$2].data = struct_table_lastentry;

			// change node's token to struct
			struct_table[struct_table_lastentry]->token = STRUCT;

			// return index in struct table
			$$ = struct_table_lastentry;
		}
		/* not decided how i should handle this type of errors yet,
		 * this i called when the id is already used by another variable
		 */
		else {
			printf("error: id already in use\n");
			$$ = -1;
		}
	}
	;

/* World syntax */
world:
      	WORLD ID new_scope statements scope_end
	{
		// Convert statements into world node
		struct ast_node *world_node = ast_table[$4];
		world_node->token = WORLD;
		world_node->value = $2;

		// return the index of the world
		$$ = $4;

		// Reset statements
		// This seems wrong, it should reset, but not this way
		ast_table_lastentry = -1;
	}
	;

/* Find statements inside worlds */
statements:
	{
		// mark statements as having no index in ast
		$$ = -1;
	}
	| statements statement
	{
		// statements have no index in ast - create one
		if ($1 == -1) {
			struct ast_node *statements = ast_create(NODE, 0);
			ast_table_lastentry++;
			ast_table[ast_table_lastentry] = statements;
			$1 = ast_table_lastentry;
		}

		// Add statement to statements
		if ($2 != -1) {
			ast_child_add(ast_table[$1], ast_table[$2]);
		}
		$$ = $1;
	}
	| statements error ';'
	;

/* statement syntax
 */
statement:
	declaration_statement
	|
	/* assignment */
	struct_ids '=' expressions ';'
	{
		struct ast_node *n = ast_table[ast_table_lastentry];

		if (symtable[ast_table[$1]->value].token != UNDEFINED) {
			//printf("assignment: %s =\n", symtable[$1].lexptr);
			//ast_print(n);

			struct ast_node *nn = ast_create('=', 0);
			//struct ast_node *n2 = ast_create(ID, $1);
			ast_child_add(nn, ast_table[$1]);
			ast_child_add(nn, ast_table[$3]);
			ast_table[++ast_table_lastentry] = nn;
			$$ = ast_table_lastentry;
		}
		else {
			printf("%d: undefined variable: %s\n", lines, symtable[$1].lexptr);
			$$ = -1;
		}
	}
	|
	/* function call
	 * comma_ids should be replaced with expressions
	 */
	struct_ids '(' comma_ids ')' ';'
	{
		struct ast_node *n = ast_create(FUNCTION_CALL, 0);
		ast_child_add(n, ast_table[$1]);
		if ($3 != -1) {
			ast_child_add(n, ast_table[$3]);
		}
		ast_table[++ast_table_lastentry] = n;
		$$ = ast_table_lastentry;
	}
	;

multiple_declaration_statement:
	{
		// mark as having no index in ast
		$$ = -1;
	}
	| multiple_declaration_statement declaration_statement
	{
		// has no index in ast - create one
		if ($1 == -1) {
			struct ast_node *node = ast_create(NODE, 0);
			ast_table_lastentry++;
			ast_table[ast_table_lastentry] = node;
			$1 = ast_table_lastentry;
		}

		// add to node
		if ($2 != -1) {
			ast_child_add(ast_table[$1], ast_table[$2]);
		}
		$$ = $1;
	}
	;

/* describes any statement that declares something
 * suitable for structs, or code that is not meant to execute expressions
 */
declaration_statement:
	variable_declaration
	|
	function_declaration
	;

variable_declaration:
	var_type ID ';'
	{
		// Undefined variable
		if (symtable[$2].token == UNDEFINED) {

			// Give it type
			symtable[$2].token = symtable[$1].token;

			// Create an abstract node for the definition and pass its index on the table
			struct ast_node *n = ast_create(DEFINE, $2);
			ast_table_lastentry++;
			ast_table[ast_table_lastentry] = n;
			$$ = ast_table_lastentry;
		}
		// Already defined
		else {
			printf("identifier %s has already been initialised\n", symtable[$2].lexptr);
			$$ = -1;
		}
	}
	;

/* Function decleration */
function_declaration:
	var_type ID '(' arguments ')' new_scope statements scope_end ';'
	{
		/* var_type == INTEGER | FLOAT */

		/* Add function to ast */
		struct ast_node *n = ast_create(FUNCTION_DECLARATION, $2);
		ast_table_lastentry++;
		ast_table[ast_table_lastentry] = n;

		/* add child arguments */
		if ($4 != -1) {
			ast_child_add(n, ast_table[$4]);
		}
		else {
			struct ast_node *node = ast_create(NODE, 0);
			ast_child_add(n, node);
		}

		/* add child statements */
		if ($7 != -1) {
			ast_child_add(n, ast_table[$7]);
		}

		// return function index
		$$ = ast_table_lastentry;
	}

expressions:
	{
		$$ = -1;
	}
	| expressions expression
	{
		// has no index in ast - create one
		if ($1 == -1) {
			struct ast_node *node = ast_create(NODE, 0);
			ast_table_lastentry++;
			ast_table[ast_table_lastentry] = node;
			$1 = ast_table_lastentry;
		}

		// Add argument to arguments
		ast_child_add(ast_table[$1], ast_table[$2]);
		$$ = $1;
	}
	;

expression:
	term '+' expression
	{
		struct ast_node *node = ast_create('+', 0);
		ast_child_add(node, ast_table[$1]);
		ast_child_add(node, ast_table[$3]);
		ast_table[++ast_table_lastentry] = node;
		$$ = ast_table_lastentry;
	}
	|	
	term '-' expression
	{

		struct ast_node *node = ast_create('-', 0);
		ast_child_add(node, ast_table[$1]);
		ast_child_add(node, ast_table[$3]);
		ast_table[++ast_table_lastentry] = node;
		$$ = ast_table_lastentry;
	}
	|
	term 
	;

term:
	factor '*' term
	{

		struct ast_node *node = ast_create('*', 0);
		ast_child_add(node, ast_table[$1]);
		ast_child_add(node, ast_table[$3]);
		ast_table[++ast_table_lastentry] = node;
		$$ = ast_table_lastentry;
	}
	|
	factor '/' term
	{

		struct ast_node *node = ast_create('/', 0);
		ast_child_add(node, ast_table[$1]);
		ast_child_add(node, ast_table[$3]);
		ast_table[++ast_table_lastentry] = node;
		$$ = ast_table_lastentry;
	}
	|
	factor
	;

factor:
	NUMBER
	{
		struct ast_node *n = ast_create(NUMBER, $1);
		ast_table_lastentry++;
		ast_table[ast_table_lastentry] = n;
		$$ = ast_table_lastentry;
	}
	|
	ID
	{
		struct ast_node *n = ast_create(ID, $1);
		ast_table_lastentry++;
		ast_table[ast_table_lastentry] = n;
		$$ = ast_table_lastentry;
	}
	;

var_type:
	INTEGER { $$ = lookup("int"); }
	|
	FLOAT { $$ = lookup("float"); }
	|
	STRING { $$ = lookup("string"); }
	|
	STRUCT ID { $$ = $2; }
	;

arguments:
	{
		// last argument to finish parsing, mark as having no index to create one
		$$ = -1;
	}
	|
	arguments argument
	{
		// arguments have no index in ast - create one
		if ($1 == -1) {
			struct ast_node *arguments = ast_create(NODE, 0);
			ast_table_lastentry++;
			ast_table[ast_table_lastentry] = arguments;
			$1 = ast_table_lastentry;
		}

		// Add argument to arguments
		ast_child_add(ast_table[$1], ast_table[$2]);
		$$ = $1;
	}
	;

argument:
	var_type ID optional_comma
	{
		// this should check if variable already exists, to avoid issues on same scope
		symtable[$2].token = symtable[$1].token;

		ast_table[++ast_table_lastentry] = ast_create(DEFINE, $2);
		$$ = ast_table_lastentry;
	}
	;

optional_comma:
	|
	','
	;

/* Handle scope */
new_scope:
	'{'
	{
		increase_scope();
	}
	;

scope_end:
	'}'
	{
		decrease_scope();
	}
	;

comma_ids:
	{
	 	$$ = -1;
	}
	|
	comma_ids optional_comma ID 
	{
		/*
		// group has no index in ast - create one
		if ($1 == -1) {
			struct ast_node *n = ast_create(NODE, 0);
			ast_table_lastentry++;
			ast_table[ast_table_lastentry] = n;
			$1 = ast_table_lastentry;
		}

		struct ast_node *n = ast_create(ID, $3);

		// Add id to group
		ast_child_add(ast_table[$1], n);
		$$ = $1;
		*/
	}
	;

struct_ids:
	ID
	{
		struct ast_node *node = ast_create(ID, $1);
		ast_table_lastentry++;
		ast_table[ast_table_lastentry] = node;
		$$ = ast_table_lastentry;
	}
	| ID '.' struct_ids
	{
		struct ast_node *node = ast_create(ID, $1);
		ast_child_add(node, ast_table[$3]);

		ast_table_lastentry++;
		ast_table[ast_table_lastentry] = node;
		$$ = ast_table_lastentry;
	}
	;
